package com.longlong.mvc;

import com.alibaba.fastjson2.JSONObject;
import com.longlong.mvc.annotation.RequestMapping;
import com.longlong.mvc.annotation.RequestParam;
import com.longlong.mvc.annotation.ResponseBody;
import com.longlong.mvc.annotation.SessionAttributes;
import com.longlong.mvc.exception.NoSuchMethodException;
import com.longlong.mvc.exception.ParameterTypeException;
import lombok.extern.slf4j.Slf4j;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.net.URL;
import java.util.*;

/**
 * @author longlong
 * @date 2025/1/25
 */
@Slf4j
public class Handler {

    /**
     * 存储  请求类名(key) === 真实类全名(value)
     * 从配置文件中获取
     * 解析类上注解获取 @RequestMapping("AtmController")
     */
    private final Map<String, String> realClassNameMap = new HashMap<>();

    /**
     * 存储 请求方法名 === 真实类全名 对应关系
     * 解析方法上注解获取  @RequestMapping("login.do")
     */
    private final Map<String, String> methodRealClassNameMap = new HashMap<>();

    /**
     * 存储 Controller 类对象
     * 真实类全名(key) === Controller类对象(value)
     * 单例模式
     * 延迟机制
     */
    private final Map<String, Object> objectMap = new HashMap<>();

    /**
     * 存储 Controller 类对象 和 对象中的全部方法
     * 类对象(key) === map(value)
     * map : 方法名(key) === 方法对象(value)
     */
    private final Map<Object, Map<String, Method>> objectMethodMap = new HashMap<>();


    /**
     * 读取配置文件
     * 获取 请求名 真实类全名 对应关系
     */
    void loadPropertiesFile() {
        try {
            log.info("start read ApplicationContext.properties ...");
            Properties properties = new Properties();
            InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ApplicationContext.properties");
            properties.load(inputStream);
            Enumeration<?> en = properties.propertyNames();
            while (en.hasMoreElements()) {
                String key = (String) en.nextElement();
                String value = properties.getProperty(key);
                log.info("根据配置文件存 key = " + key + " value = " + value);
                realClassNameMap.put(key, value);
            }
            log.info("end read ApplicationContext.properties ...");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描 controller 包中的类
     *
     * @param packageNames 包名
     */
    void scanAnnotation(String packageNames) {
        log.info("start scan package...");
        // 包名 com.longlong.controller
        // 多个包名用逗号隔开
        String[] packages = packageNames.split(",");
        // 解析数组中的每一个包名字 当然也有可能就一个名字
        for (String packageName : packages) {
            log.info("packageName = " + packageName);
            URL url = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "\\"));
            if (url == null) {
                continue;
            }
            //  根据获取的url定位一个真实类文件路径
            String packagePath = url.getPath();
            log.info("packagePath = " + packagePath);

            //  根据 packagePath 创建一个 File 对象
            File packageFile = new File(packagePath);
            //  获取包中所有类文件
            File[] files = packageFile.listFiles(file -> file.isFile() && file.getName().endsWith("class"));
            int count = 1;
            for (File file : files) {
                log.info("==========  NO." + count++ + " file  ==========");
                //  获取了文件名 AtmController.class
                String simpleName = file.getName();
                log.info("simpleName = " + simpleName);
                //  获取类全名 com.longlong.controller.AtmController
                String fullName = packageName + "/" + simpleName.substring(0, simpleName.indexOf("."));
                fullName = fullName.replace("/", ".");
                log.info("fullName = " + fullName);
                try {
                    Class<?> clazz = Class.forName(fullName);
                    RequestMapping classAnnotation = clazz.getAnnotation(RequestMapping.class);
                    log.info("classAnnotation : " + classAnnotation);
                    if (classAnnotation != null) {
                        // controller 类上有注解
                        String requestName = classAnnotation.value();
                        log.info("requestName = " + requestName);
                        log.info("fullName = " + fullName);
                        log.info("根据注解存 key = " + requestName + " value = " + fullName);
                        realClassNameMap.put(requestName, fullName);
                        // 此时 和读文件 完成了一样的效果
                    } else {
                        // controller 类上没有注解
                        Method[] methods = clazz.getDeclaredMethods();
                        for (Method method : methods) {
                            RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
                            if (methodAnnotation == null) {
                                continue;
                            }
                            methodRealClassNameMap.put(methodAnnotation.value(), fullName);
                        }
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        log.info("end scan package...");
    }


    /**
     * 解析请求路径
     *
     * @param uri 请求路径 URI : /xxx/AtmController.do
     * @return 类名 : AtmController
     */
    String parseURI(String uri) {
        return uri.substring(uri.lastIndexOf("/") + 1, uri.indexOf("."));
    }

    /**
     * 通过类名获取类对象
     *
     * @param requestContent 请求路径中的类名 AtmController
     * @return 类对象 AtmController 对象
     * @throws ClassNotFoundException 找不到类
     * @throws IllegalAccessException 异常
     * @throws InstantiationException 异常
     */
    Object findObject(String requestContent, String typeName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        // 从集合中获取
        Object obj = objectMap.get(requestContent);
        if (obj != null) {
            return obj;
        }
        // 从集合中获取不到 还未创建对象
        // 获取类全名
        String fullClassName = null;
        if ("methodName".equals(typeName)) {
            // 发送的请求是 方法名.do
            fullClassName = methodRealClassNameMap.get(requestContent);
        } else if ("className".equals(typeName)) {
            // 发送的请求是 类名.do
            fullClassName = realClassNameMap.get(requestContent);
        } else {
            throw new RuntimeException("参数错误");
        }
        Class<?> clazz = Class.forName(fullClassName);
        obj = clazz.newInstance();
        log.info("成功加载 " + fullClassName + " 类 并创建了 类对象");
        objectMap.put(fullClassName, obj);
        //  获取当前类中全部的方法
        log.info("开始加载 " + fullClassName + " 类中的全部方法");
        Method[] methods = clazz.getDeclaredMethods();
        Map<String, Method> methodMap = new HashMap<>(10);
        for (Method method : methods) {
            //方法名字和方法对象存入 map 集合里 Map<methodName,method>
            methodMap.put(method.getName(), method);
            log.info("方法名 = " + method.getName() + " 方法对象 = " + method);
        }
        // Map<AtmController,Map<methodName,method>>
        objectMethodMap.put(obj, methodMap);
        log.info("结束加载 " + fullClassName + " 类中的全部方法");
        return obj;
    }

    /**
     * 获取方法对象
     *
     * @param obj        类对象
     * @param methodName 方法名
     * @return 方法对象
     */
    Method findMethod(Object obj, String methodName) {
        Map<String, Method> methodMap = objectMethodMap.get(obj);
        return methodMap.get(methodName);
    }

    /**
     * 解析方法参数 获取参数值
     *
     * @param method   方法对象
     * @param request  请求对象
     * @param response 响应对象
     * @return 参数值数组
     * @throws IllegalAccessException    异常
     * @throws InstantiationException    异常
     * @throws NoSuchMethodException     异常
     * @throws InvocationTargetException 异常
     */
    Object[] injectionParameters(Method method, HttpServletRequest request, HttpServletResponse response) throws IllegalAccessException, InstantiationException, InvocationTargetException, java.lang.NoSuchMethodException {
        //  获取method方法中所有参数对象
        Parameter[] parameters = method.getParameters();
        //  判断当前方法是否有参数
        if (parameters == null || parameters.length == 0) {
            log.info(method.getName() + " 方法没有参数 不需要做参数注入");
            return new Object[0];
        }

        //  解析参数
        //  参数类型
        //  基本类型 String int long float double 配合注解使用 @RequestParam
        //  对象类型 domain
        //  集合类型 map
        Object[] finalParamValue = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            //  获取参数注解
            RequestParam paramAnnotation = parameter.getAnnotation(RequestParam.class);
            if (paramAnnotation != null) {
                //  有注解 基本类型
                //  public String login(@RequestParam("name")String name,@RequestParam("pass")int pass)
                String key = paramAnnotation.value();
                //  获取参数值
                String value = request.getParameter(key);
                if (value != null) {
                    // 基本类型 String int long float double
                    // 获取当前参数的数据类型
                    Class<?> paramClazz = parameter.getType();
                    //分析参数类型做分支判断
                    if (paramClazz == String.class) {
                        finalParamValue[i] = value;
                    } else if (paramClazz == Integer.class || paramClazz == int.class) {
                        finalParamValue[i] = new Integer(value);
                    } else if (paramClazz == Float.class || paramClazz == float.class) {
                        finalParamValue[i] = new Float(value);
                    } else if (paramClazz == Double.class || paramClazz == double.class) {
                        finalParamValue[i] = new Double(value);
                    } else if (paramClazz == Long.class || paramClazz == long.class) {
                        finalParamValue[i] = new Long(value);
                    }
                }
            } else {
                //  没有注解(3种情况)
                //  1. 原生 request response
                //  2. domain 对象
                //  3. map
                Class<?> paramClazz = parameter.getType();
                if (paramClazz.isArray()) {
                    // 数组类型 不支持
                    throw new ParameterTypeException("方法内数组参数无法处理");
                } else {
                    if (paramClazz == HttpServletRequest.class) {
                        finalParamValue[i] = request;
                        continue;
                    }
                    if (paramClazz == HttpServletResponse.class) {
                        finalParamValue[i] = response;
                        continue;
                    }
                    if (paramClazz == Map.class || paramClazz == List.class) {
                        //  接口 不支持
                        throw new ParameterTypeException("方法内集合不能传递接口 请提供具体类型");
                    }
                    // 普通具体对象
                    Object paramObj = paramClazz.newInstance();
                    if (paramObj instanceof Map) {
                        //  map类型
                        Map<String, Object> paramMap = (Map<String, Object>) paramObj;
                        //  请求的 参数 和 参数值 转化为 map 的 key 和 value
                        Enumeration<String> en = request.getParameterNames();
                        while (en.hasMoreElements()) {
                            String key = en.nextElement();
                            String value = request.getParameter(key);
                            paramMap.put(key, value);
                        }
                        finalParamValue[i] = paramMap;
                    } else if (paramObj instanceof Object) {
                        // domain 对象
                        // 请求的参数名 和 对象的属性名 对应
                        Field[] fields = paramClazz.getDeclaredFields();
                        for (Field field : fields) {
                            field.setAccessible(true);
                            //  获取属性名
                            String key = field.getName();
                            //  获取属性值
                            String value = request.getParameter(key);
                            Class fieldType = field.getType();
                            Constructor fieldContructor = fieldType.getConstructor(String.class);
                            // 给对应的属性赋值（简单属性 String Integer）
                            field.set(paramObj, fieldContructor.newInstance(value));
                            // 下列类型的属性不支持
                            // Character类型
                            // 实体对象类型(递归)
                        }
                        finalParamValue[i] = paramObj;
                    } else {
                        throw new ParameterTypeException(method + " 方法 检测到未知类型参数");
                    }
                }
            }
        }
        return finalParamValue;
    }


    /**
     * 解析方法返回值
     *
     * @param obj          controller对象
     * @param method       controller对象中的方法对象
     * @param methodResult 方法执行后的返回值
     * @param request      请求对象
     * @param response     响应对象
     * @throws ServletException 异常
     * @throws IOException      异常
     */
    void finalResolver(Object obj, Method method, Object methodResult, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//解析器
        // methodResult 分情况
        //      null : 不需要响应信息
        //      ModelAndView : viewName + map
        //      String : viewName 重定向路径 或 数据 通过注解区分
        //      Object : 返回对象  JSON
        if (methodResult == null) {
            log.info("不需要响应信息");
            return;
        }
        if (methodResult instanceof ModelAndView) {
            ModelAndView mv = (ModelAndView) methodResult;
            this.parseModelAndView(obj, mv, request);
            this.parseResponseContent(mv.getViewName(), request, response);
        } else if (methodResult instanceof String) {
            ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
            if (responseBody != null) {
                //  有注解 证明返回值是一个数据
                response.setContentType("text/html;charset=UTF-8");
                response.getWriter().write((String) methodResult);
                log.info("本次响应方式为 String 字符串数据");
            } else {
                //  没有注解 证明返回值是一个路径
                this.parseResponseContent((String) methodResult, request, response);
            }
        } else {
            //  返回值可能是一些对象  domain  List<domain>
            //  AJAX+JSON
            ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
            if (responseBody != null) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("jsonObject", methodResult);
                response.getWriter().write(jsonObject.toJSONString());
                log.info("本次响应方式为 JSON 数据");
            } else {
                log.info("返回值类型无法识别...");
            }
        }
    }

    /**
     * 解析 ModelAndView 对象
     *
     * @param obj     controller对象
     * @param mv      ModelAndView对象
     * @param request 请求对象
     */
    private void parseModelAndView(Object obj, ModelAndView mv, HttpServletRequest request) {
        // 取出 map 中的所有数据 存入request作用域中
        // request.setAttribute
        HashMap<String, Object> mvMap = mv.getAttributeMap();
        Set<String> keys = mvMap.keySet();
        for (String key : keys) {
            Object value = mvMap.get(key);
            request.setAttribute(key, value);
        }
        // 取出 map 中指定数据存入session作用域中
        // session.setAttribute
        SessionAttributes sattr = obj.getClass().getAnnotation(SessionAttributes.class);
        if (sattr != null) {
            String[] attributeNames = sattr.value();
            if (attributeNames.length != 0) {
                HttpSession session = request.getSession();
                for (String attributeName : attributeNames) {
                    session.setAttribute(attributeName, mvMap.get(attributeName));
                }
            }
        }

    }

    /**
     * 解析 viewName 完成 转发 or 重定向
     *
     * @param viewName viewName
     * @param request  请求对象
     * @param response 响应对象
     * @throws ServletException 异常
     * @throws IOException      异常
     */
    private void parseResponseContent(String viewName, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (viewName == null || "".equals(viewName)) {
            return;
        }
        String[] value = viewName.split(":");
        // 转发  xxx.jsp
        if (value.length == 1) {
            log.info("本次响应方式为转发 path :" + viewName);
            request.getRequestDispatcher(viewName).forward(request, response);
            return;
        }
        // 重定向  redirect:xxx.jsp
        if ("redirect".equals(value[0])) {
            log.info("本次响应方式为重定向 path :" + value[1]);
            response.sendRedirect(value[1]);
            return;
        }
        log.info("无法识别的视图解析方式");
    }

}
